﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Data.Linq;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MTGenome.MTG;

namespace MTGenome.Gatherer
{
    [Export(typeof(IGathererServer)), PartCreationPolicy(CreationPolicy.Shared)]
    public sealed class GathererServer : IGathererServer
    {
        public static readonly string _dataDirectory = null,
            _cacheDirectory = null;

        static GathererServer()
        {
            _dataDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            _cacheDirectory = Path.Combine(_dataDirectory, "Cache");
        }

        public GathererServer()
        {
            _offline = !NetworkInterface.GetIsNetworkAvailable();
            NetworkChange.NetworkAvailabilityChanged += (sender, e) => Offline = !e.IsAvailable;
        }

        public string DataDirectory
        {
            get { return _dataDirectory; }
        }

        public string CacheDirectory
        {
            get { return _cacheDirectory; }
        }

        /// <summary>
        /// Event when the ForGatherer method finds a card
        /// </summary>
        public event EventHandler<EventArgs<ICard>> Enumerated = delegate { };
        public event EventHandler OfflineChanged = delegate { };

        private HttpClient _client = new HttpClient();
        private readonly object _lock = new object();
        private bool _offline = false;

        public bool Offline
        {
            get { return _offline; }
            set
            {
                if (_offline != value)
                {
                    _offline = value;
                    OfflineChanged(null, EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// Enumerates gatherer website using parallelism, Enumerated event pushes found cards
        /// --Note that this method may block indefinitely
        /// </summary>
        /// <param name="start">MultiverseID to start with</param>
        /// <param name="start">MultiverseID to end with</param>
        public void ForGatherer(int start, int end)
        {
            Parallel.For(start, end, i =>
            {
                ICard card = GetCard(i);
                OnEnumerated(card);
            });
        }

        /// <summary>
        /// Enumerates gatherer website using parallelism, Enumerated event pushes found cards
        /// --Note that this method may block indefinitely
        /// </summary>
        public void ForEachGatherer(bool reverse = false)
        {
            string path = GetFile(Constants.Urls.CardList, _cacheDirectory + "\\CardList.html");

            Parallel.ForEach(reverse ? ForEachCard(path).Reverse() : ForEachCard(path), i =>
            {
                Card card = GetCard(i) as Card;
                OnEnumerated(card);

                if (card.OtherMultiverseIDs != null)
                {
                    foreach (int otherID in card.OtherMultiverseIDs)
                    {
                        card = GetCard(otherID) as Card;
                        OnEnumerated(card);
                    }
                }
            });
        }

        /// <summary>
        /// Gets the card info from the gatherer website
        /// </summary>
        /// <param name="multiverseID">ID of card to retrieve</param>
        /// <returns>Card info</returns>
        public ICard GetCard(int multiverseID)
        {
            return HtmlParser.Parse(multiverseID,
                _client.Get(Constants.Urls.CardInfo,
                    new Dictionary<string, string> { { Constants.Keys.MultiVerseID, multiverseID.ToString() } }));
        }

        private void OnEnumerated(ICard card)
        {
            if (!string.IsNullOrEmpty(card.Text) && !string.IsNullOrEmpty(card.Expansion))
            {
                Enumerated(null, new EventArgs<ICard> { Value = card });
            }
        }

        private IEnumerable<int> ForEachCard(string path)
        {
            using (FileStream stream = File.OpenRead(path))
            {
                using (StreamReader reader = new StreamReader(stream))
                {
                    string text = null;
                    do
                    {
                        text = reader.ReadLine();
                        foreach (int id in HtmlParser.ParseMultiverseIDs(text))
                        {
                            yield return id;
                        }
                    } while (!reader.EndOfStream);
                }
            }
        }

        /// <summary>
        /// Returns path to image of card, given card's id
        /// --Downloads image, if not cached
        /// </summary>
        /// <param name="multiverseID">ID of card</param>
        /// <returns>Absolute path to image file</returns>
        public string GetImagePath(int multiverseID)
        {
            return GetImage("Card-", multiverseID.ToString(), new Dictionary<string, string>
                {
                    { Constants.Keys.MultiVerseID, multiverseID.ToString() },
                    { Constants.Keys.ImageType, ImageType.Card.ToString().ToLower() },
                    { Constants.Keys.ImageSize, ImageSize.Small.ToString().ToLower() },
                });
        }

        /// <summary>
        /// Returns path to image of symbol, given symbol's name
        /// --Downloads image, if not cached
        /// </summary>
        /// <param name="name">Name of symbol</param>
        /// <param name="size">Size of image</param>
        /// <returns>Absolute path to image file</returns>
        public string GetImagePath(string name, ImageSize size)
        {
            return GetImage("Symbol-" + size + "-", name, new Dictionary<string, string>
                {
                    { Constants.Keys.ImageName, name },
                    { Constants.Keys.ImageType, ImageType.Symbol.ToString().ToLower() },
                    { Constants.Keys.ImageSize, size.ToString().ToLower() },
                });
        }

        /// <summary>
        /// Returns path to image of symbol, given symbol's name
        /// --Downloads image, if not cached
        /// </summary>
        /// <param name="name">Name of symbol</param>
        /// <param name="size">Size of image</param>
        /// <returns>Absolute path to image file</returns>
        public string GetImagePath(string name, Rarity rarity, ImageSize size)
        {
            return GetImage("Symbol-" + size + "-" + rarity + "-", name, new Dictionary<string, string>
                {
                    { Constants.Keys.ImageSet, name },
                    { Constants.Keys.ImageType, ImageType.Symbol.ToString().ToLower() },
                    { Constants.Keys.ImageSize, size.ToString().ToLower() },
                    { Constants.Keys.ImageRarity, rarity.ToString().ToLower()[0].ToString() }
                });
        }

        private string GetImage(string prefix, string id, Dictionary<string, string> values)
        {
            lock (_lock)
            {
                if (!Directory.Exists(_cacheDirectory))
                {
                    Directory.CreateDirectory(_cacheDirectory);
                }

                string path = _cacheDirectory + "\\" + prefix + id + ".jpg";
                if (!File.Exists(path) || new FileInfo(path).Length == 0)
                {
                    if (Offline)
                    {
                        return null;
                    }
                    _client.DownloadFile(Constants.Urls.Image, path, values);
                }
                return path;
            }
        }

        private string GetFile(string url, string path)
        {
            lock (_lock)
            {
                if (!Directory.Exists(_cacheDirectory))
                {
                    Directory.CreateDirectory(_cacheDirectory);
                }

                if (!File.Exists(path) || new FileInfo(path).Length == 0)
                {
                    _client.DownloadFile(url, path);
                }
                return path;
            }
        }
    }
}
